This repository has no description
1import { error, redirect } from "@sveltejs/kit";
2import { createBobbinClient } from "$lib/api/client";
3import { count } from "$lib/api/count";
4import { enrich, handleOf, TYPE_MINIDOC } from "$lib/api/enrich";
5import { gitTarget, resolveDefaultBranch } from "$lib/api/gitclient";
6import { getStarRkey } from "$lib/api/graph";
7import { resolveMiniDoc } from "$lib/api/identity";
8import { parallel, toHttpError } from "$lib/api/load";
9import type { RecordView, RepoRecord } from "$lib/api/records";
10import { repoNameOf, resolveRepoByName } from "$lib/api/repo";
11import { didFromUri, rkeyFromUri } from "$lib/api/uri";
12import type { BobbinContext } from "$lib/api/client";
13import type { RepoCounts, RepoInfo, RepoSource } from "$lib/components/repo/types";
14import type { LayoutLoad } from "./$types";
15
16const FALLBACK_BRANCH = "main";
17
18const resolveSource = async (
19 ctx: BobbinContext,
20 uri: string | undefined
21): Promise<RepoSource | null> => {
22 if (!uri?.startsWith("at://")) return null;
23 try {
24 const page = await enrich<RecordView<RepoRecord>>(ctx, {
25 xrpc: "sh.tangled.repo.getRepo",
26 params: { repo: uri },
27 enrich: [{ source: "sh.tangled.repo:.repo", type: TYPE_MINIDOC, targets: ["uri"] }]
28 });
29 const view = page.output;
30 const ownerDid = didFromUri(view.uri);
31 return {
32 ownerHandle: handleOf(page.data, ownerDid, "sh.tangled.repo:.repo"),
33 name: repoNameOf(view)
34 };
35 } catch {
36 // a fork whose source is gone still renders, just without the attribution
37 return null;
38 }
39};
40
41// the layout reset also cuts us off from `[handle]/+layout.ts`, so identity gets
42// resolved again here
43export const load: LayoutLoad = async (event) => {
44 const parent = await event.parent();
45 const identifier = decodeURIComponent(event.params.handle);
46 const name = decodeURIComponent(event.params.repo);
47
48 // rejects bare words so unrelated paths 404 instead of resolving as actors
49 if (!identifier.startsWith("did:") && !identifier.includes(".")) {
50 error(404, "Not found");
51 }
52
53 const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch });
54 const doc = await resolveMiniDoc(ctx, identifier).catch((cause) =>
55 toHttpError(cause, "Could not resolve user")
56 );
57
58 // redirects dids and stale handles to the canonical handle url
59 const canonical = doc.handle && !doc.handle.endsWith(".invalid") ? doc.handle : null;
60 if (canonical && identifier.toLowerCase() !== canonical.toLowerCase()) {
61 redirect(307, `/${canonical}/${event.params.repo}${event.url.search}`);
62 }
63
64 const view = await resolveRepoByName(ctx, doc.did, name).catch((cause) =>
65 toHttpError(cause, "Could not load repository")
66 );
67 if (!view) error(404, `${doc.handle}/${name} does not exist`);
68
69 const record = view.value;
70 const repoDid = record.repoDid;
71 const viewerDid = parent.auth?.did;
72 const git = gitTarget(parent.publicConfig, { uri: view.uri, repoDid }, event.fetch);
73
74 const stats = await parallel({
75 // only the git backend knows the default branch, and one that is down
76 // or still syncing shouldn't take out the whole layout
77 defaultBranch: resolveDefaultBranch(git),
78 stars: repoDid
79 ? count(ctx, "sh.tangled.feed.countStars", repoDid).catch(() => null)
80 : Promise.resolve(null),
81 // the tabs count what is still open, closed ones matter on their own pages
82 issues: repoDid
83 ? count(ctx, "sh.tangled.repo.countIssues", repoDid, { state: "open" }).catch(() => null)
84 : Promise.resolve(null),
85 pulls: repoDid
86 ? count(ctx, "sh.tangled.repo.countPulls", repoDid, { status: "open" }).catch(() => null)
87 : Promise.resolve(null),
88 forks: repoDid
89 ? count(ctx, "sh.tangled.repo.countForks", repoDid).catch(() => null)
90 : Promise.resolve(null),
91 viewerStarRkey:
92 viewerDid && repoDid
93 ? getStarRkey(ctx, viewerDid, repoDid).catch(() => null)
94 : Promise.resolve(null),
95 source: resolveSource(ctx, record.source)
96 });
97
98 const repo: RepoInfo = {
99 uri: view.uri,
100 rkey: rkeyFromUri(view.uri),
101 name: repoNameOf(view),
102 ownerDid: doc.did,
103 ownerHandle: doc.handle,
104 repoDid,
105 knot: record.knot,
106 spindle: record.spindle,
107 description: record.description,
108 website: record.website,
109 topics: record.topics,
110 source: stats.source ?? undefined,
111 defaultBranch: stats.defaultBranch ?? FALLBACK_BRANCH
112 };
113
114 const counts: RepoCounts = {
115 stars: stats.stars?.count ?? 0,
116 issues: stats.issues?.count ?? 0,
117 pulls: stats.pulls?.count ?? 0,
118 forks: stats.forks?.count ?? 0
119 };
120
121 return { repo, counts, viewerStarRkey: stats.viewerStarRkey };
122};